package org.umlviewer.xmiparser;

import org.umlviewer.model.diagram_class.Clase;
import org.umlviewer.model.diagram_class.Param;
import org.umlviewer.model.diagram_class.TypedElement;
import org.umlviewer.model.diagram_class.Attribute;
import org.umlviewer.model.diagram_class.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.umlviewer.model.DiagramElement;
import org.umlviewer.model.Relationship;
import org.umlviewer.model.Constants;
import org.umlviewer.model.UmlDiagram;
import org.umlviewer.model.VisibilityKind;
import org.umlviewer.model.usecase_diagram.Actor;
import org.umlviewer.model.usecase_diagram.UseCase;
import org.umlviewer.model.sequence_diagram.LifeLine;
import org.jdom2.Element;
/**
 *
 * @author Rafa
 */
//parser para procesar xmi v2.1
public class Xmi21Parser extends XmiParser {
    
    //diagrama que se devuelve, se hace de clase por el metodo recursivo
    private UmlDiagram retorno;
    //las relaciones se procesan despues de crear los elementos    
    private ArrayList<RelationshipParser> relaciones;    
    private String attname, reltype, auxStr;
    private Element auxElement;
    //mapa con los tipos de datos generados por las clases que se extraen
    private HashMap<String, String> datatypes;
    //mapa para almacenar las referencias hacia los LifeLine
    private HashMap<String, String> occurrenceSpecification;
    //lista de elementos con tipos de dato por resolver
    private ArrayList<TypedElement> typedelements;
    //resolver los tipos de asocioaciones
    private HashMap<String, Integer> relationsTypes;
    //transitorio, buscar otra manera de asignar los valores
    private HashMap<String, String> relationsNames;
    
    public Xmi21Parser()
    {
        super();
        retorno = new UmlDiagram();      
        relaciones = new ArrayList<RelationshipParser>();
        datatypes = new HashMap<String, String>();
        typedelements = new ArrayList<TypedElement>();
        occurrenceSpecification = new HashMap<String, String>();
        relationsTypes = new HashMap<String, Integer>();
        relationsNames = new HashMap<String, String>();
    }
    
    @Override
    public UmlDiagram getUmlDiagram(Element xmiRoot)
    {        
        parseXmi(xmiRoot, "", null);
        //agrgar las relaciones entre elementos
        DiagramElement srcElement, dstElement;                
        String idSrc, idDst;
        //TODO: mejorar el parseo de relaciones, evitar muchos ciclos (for)        
        for(RelationshipParser r:relaciones)        
        {            
//            System.out.println("R - ORG: "+r.getIdSource());
//            System.out.println("R - DST: "+r.getIdDestination());
//            System.out.println("------");
            if(r.getType() == Relationship.MESSAGE){
                idSrc = occurrenceSpecification.get(r.getIdSource());
                idDst = occurrenceSpecification.get(r.getIdDestination());
                srcElement = retorno.getElmentById(idSrc);
                dstElement = retorno.getElmentById(idDst);
            }else{
                srcElement = retorno.getElmentById(r.getIdSource());
                dstElement = retorno.getElmentById(r.getIdDestination());
                //se busca el tipo de relacion
                if(relationsTypes.containsKey(r.getRelId())){
                    r.setType(relationsTypes.get(r.getRelId()).intValue());
                    if(r.getName()==null || r.getName().equals("") )
                        r.setName(relationsNames.get(r.getRelId()));
                }
            }
            //se verifica que los nodos de origen y destino se encuentran en el diagrama
            if(srcElement!=null && dstElement!=null){
                //Relationship rel = new Association(r.getName(), r.getType(), srcElement, dstElement);
                Relationship rel = r.getRelationship(srcElement, dstElement);
                retorno.addRelationship(rel);
            }
        }
        //se procesan los tipos de datos que estan sin resolver
        if(!typedelements.isEmpty()){
            for(TypedElement te : typedelements){
                if(datatypes.containsKey(te.getType())){
                    te.setType(datatypes.get(te.getType()));
                }
            }
        }
        
        return retorno;
    }
        
    /***
     * Funcion recursiva para leer cada nodo del xmi e instanciar cada objeto
     * del diagrama
     * @param xmiRoot 
     * @param idref xmi:id del nodo padre elemento procesado
     */
    private void parseXmi(Element xmiRoot, String idref, DiagramElement parent)
    {
        List<Element> hijos;
        hijos = xmiRoot.getChildren();        
        String elementType;
        String elementId;
        idref = xmiRoot.getAttributeValue(("id"),getXmiNamespace(),"");
        for(Element e: hijos)
        {                                
            elementType = e.getAttributeValue("type",getXmiNamespace(), "");                        
            elementId = e.getAttributeValue(("id"),getXmiNamespace(),"");
            //TODO: revisar que otra validacion se puede hacer
            //if(!aux.equals(""))
            //{
                if(elementType.equals("uml:Class"))
                {
                    Clase c = new Clase(e.getAttributeValue("name"));
                    c.setId(elementId);                    
                    c.setAbstract(getBoolean(e.getAttributeValue("isAbstract")));
                    retorno.addElement(c);                    
                    //se aniade la clase a los tipos de datos
                    datatypes.put(elementId, c.getName());
                    parent = c;
                }
                else if(elementType.equals("uml:Actor"))
                {
                    Actor ac = new Actor(e.getAttributeValue("name"));
                    ac.setId(elementId);
                    retorno.addElement(ac);                             
                }
                else if(elementType.equals("uml:UseCase"))
                {
                    UseCase uso = new UseCase(e.getAttributeValue("name"));
                    uso.setId(elementId);
                    retorno.addElement(uso);        
                    parent = uso;
                }
                else if(elementType.equals("uml:Lifeline")){
                    LifeLine lf = new LifeLine(e.getAttributeValue("name","")); //TODO: cuando hay represents
                    lf.setId(elementId);
                    retorno.addElement(lf);
                }
                else if(elementType.equals("uml:Generalization") )
                {
                    RelationshipParser rparser = new RelationshipParser(idref, e.getAttributeValue("general"), Relationship.GENERALIZATION);
                    relaciones.add(rparser);                    
                }
                else if(elementType.equals("uml:Realization") )
                {
                    RelationshipParser rparser = new RelationshipParser(e.getAttributeValue("client"), e.getAttributeValue("supplier"), Relationship.REALITATION);
                    rparser.setName(e.getAttributeValue("name",""));
                    relaciones.add(rparser);       
                }
                //atributos de las clases
                else if(e.getName().equals("ownedAttribute"))
                {
                    if (elementType.equals("uml:Property")) {
                        //algunas relaciones se incluyen como ownedattribute en la clase                        
                        //es una asociacion
                        if (e.getAttributeValue("association") != null) {
                            RelationshipParser rparser = new RelationshipParser(idref, e.getChild("type").getAttributeValue("idref", getXmiNamespace(), ""), 1);
                            rparser.setRelId(e.getAttributeValue("association"));
                            rparser.setName(e.getAttributeValue("name", ""));
                            relaciones.add(rparser);
                        }
                        else{//es una atributo
                            attname = e.getAttributeValue("name");
                            if (attname != null && !attname.equals("")) { //es un atributo
                                parseAttribute(e, parent);
                            }
                        }                        
                    }                   
                }
                //metodos de la clase               
                else if(e.getName().equals("ownedOperation")){                    
                    parseMethod(e, parent);
                }
                //TODO revisar doc de las asociaciones
                else if(elementType.equals("uml:Association")){
                    parseAssociation(e);
                }
                else if(elementType.equals("uml:Include"))
                {
                    if(parent!=null)
                    {
                        RelationshipParser rp = new RelationshipParser();                                                
                        rp.setIdSource(idref);
                        rp.setIdDestination(e.getAttributeValue("addition"));
                        rp.setType(Relationship.INCLUDE);
                        rp.setName("<<include>>");
                        relaciones.add(rp);
                    }
                }
                else if(elementType.equals("uml:Extend")){
                    if(parent!=null){
                        RelationshipParser rp = new RelationshipParser();
                        rp.setIdSource(idref);
                        rp.setIdDestination(e.getAttributeValue("extendedCase"));
                        rp.setType(Relationship.EXTEND);
                        rp.setName("<<extend>>");
                        relaciones.add(rp);
                    }
                }
                else if(elementType.equals("uml:OccurrenceSpecification")){                    
                    occurrenceSpecification.put(elementId, e.getAttributeValue("covered"));
                }
                else if(elementType.equals("uml:Message")){
                    RelationshipParser rm = new RelationshipParser(); 
                    rm.setIdSource(e.getAttributeValue("sendEvent"));
                    rm.setIdDestination(e.getAttributeValue("receiveEvent"));
                    rm.setName(e.getAttributeValue("name",""));
                    rm.setType(Relationship.MESSAGE);
                    relaciones.add(rm);
                }
            //}                    
            //System.out.println("Etiqu: "+e.getName());
            parseXmi(e, idref, parent);
        }        
    }
    
    /***
     * Devuelve la constante que representa la visibilidad del param pasado
     * @param v visibilidad public, private
     * @return 
     */       
    private VisibilityKind getVisibilty(String v){
        if(v==null)
            return VisibilityKind.V_PUBLIC;
        
        if(v.equals("public"))
            return VisibilityKind.V_PUBLIC;
        else if(v.equals("private"))
            return VisibilityKind.V_PRIVATE;
        else if(v.equals("package"))
            return VisibilityKind.V_PACKAGE;
        else if(v.equals("protected"))
            return VisibilityKind.V_PROTECTED;
        
        return VisibilityKind.V_PUBLIC;
    }
    
    private boolean getBoolean(String s)
    {
        return (s!=null && s.equals("true")); 
    }
    
    //leer atributos para las clases
    private void parseAttribute(Element e, DiagramElement parent) {        
        if (parent != null) {
            if (parent instanceof Clase) {
                Clase cl = (Clase) parent;
                Attribute att = new Attribute();
                attname = e.getAttributeValue("name");
                att.setName(attname);
                auxElement = e.getChild("type");
                auxStr = "";
                if (auxElement != null) {
                    auxStr = auxElement.getAttributeValue("idref", getXmiNamespace(), "");
                    //suprimir prefijo que agrega EA
                    auxStr = auxStr.replace("EAJava_", "");
                    //se aniade el atributo para procesar su tipo despues
                    typedelements.add(att);
                }
                att.setType(auxStr);
                att.setVisibility(getVisibilty(e.getAttributeValue("visibility")));
                att.setStatic(getBoolean(e.getAttributeValue("isStatic")));
                cl.addAttribute(att);
            }
        }
    }
    
    //leer metodos
    private void parseMethod(Element e, DiagramElement parent) {
        if (parent != null) {
            if (parent instanceof Clase) {
                Clase cl = (Clase) parent;
                Method mt = new Method();
                mt.setName(e.getAttributeValue("name"));
                mt.setVisibility(getVisibilty(e.getAttributeValue("visibility")));
                mt.setAbstrac(getBoolean(e.getAttributeValue("isAbstract")));
                mt.setStatic(getBoolean(e.getAttributeValue("isStatic")));
                //los parametros
                List<Element> params = e.getChildren("ownedParameter");
                for (Element p : params) {
                    //TODO: cuando el parametro viene en kind
                    //a veces se utiliza la propiedad kind ={return, inout} para el tipo de param
                    if (p.getAttributeValue("direction") != null) {
                        if (p.getAttributeValue("direction").equals("in")) {
                            Param param = new Param();
                            param.setName(p.getAttributeValue("name"));
                            //suprimir prefijo que agrega EA
                            auxStr = p.getAttributeValue("type");
                            auxStr = auxStr.replace("EAJava_", "");
                            param.setType(auxStr);
                            typedelements.add(param);
                            mt.addParam(param);                        
                        }                         
                        else {
                            //el parametro tipo out se asigna como retorno
                            auxStr = p.getAttributeValue("type","");
                            auxStr = auxStr.replace("EAJava_", "");
                            mt.setType(auxStr);
                            typedelements.add(mt);
                        }
                    }
                }
                cl.addMethod(mt);
            }
        }
    }
    
    private void parseAssociation(Element e) {
        String elementId = e.getAttributeValue(("id"),getXmiNamespace(),"");
        RelationshipParser rp = null;
        String source = "", dest = "";
        int typeAux = 0;
        //el nombre de la relacion
        auxStr = e.getAttributeValue("name");
            if (auxStr == null) {
            auxStr = "";
        }
        //para las relaciones que incluyen dos hijos ownedEnd
        List<Element> relnodes = e.getChildren("ownedEnd");
        if (relnodes != null && relnodes.size() >= 2) {
            rp = new RelationshipParser();
            //el primer hijo es el destino
            if (relnodes.get(0).getChild("type") != null) {
                dest = relnodes.get(0).getChild("type").getAttributeValue(("idref"), getXmiNamespace(), "");
            } //cuando no posee el hijo type, se lee el atributo type
            else {
                dest = relnodes.get(0).getAttributeValue("type");
            }
            if (relnodes.get(1).getChild("type") != null) {
                source = relnodes.get(1).getChild("type").getAttributeValue(("idref"), getXmiNamespace(), "");
            } else {
                source = relnodes.get(1).getAttributeValue("type");
            }
            rp.setIdDestination(dest);
            rp.setIdSource(source);
            rp.setType(Relationship.ASSOCIATION);            
            rp.setName(auxStr);
            relaciones.add(rp);
        } //se busca el tipo de asociacion
        else {
            /*for (RelationshipParser r : relaciones) {
                if (r.getRelId().equals(elementId)) {
                    rp = r;
                    break;
                }
            }*/
            //if (rp != null) {
            
                reltype = e.getChild("ownedEnd").getAttributeValue("aggregation");
                if (reltype.equals("shared")) {
                    typeAux = Relationship.AGGREGATION;
                } else if (reltype.equals("composite")) {
                    typeAux = Relationship.COMPOSITION;
                } else {
                    typeAux = Relationship.ASSOCIATION;
                }
                relationsTypes.put(elementId, typeAux);
                relationsNames.put(elementId, auxStr);
            //}
        }
    }
}
